package com.siyoumi.app.netty;

import com.siyoumi.app.netty.entity.EnumNettyErr;
import com.siyoumi.app.netty.entity.NettyMsg;
import com.siyoumi.app.netty.entity.NettyRoomUser;
import com.siyoumi.component.XRedis;
import com.siyoumi.component.XApp;
import com.siyoumi.util.LogMdc;
import com.siyoumi.util.XJson;
import com.siyoumi.util.XStr;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketCloseStatus;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//服务端-消息处理-逻辑类
@Slf4j
public abstract class NettyServerChannelHandler
        extends SimpleChannelInboundHandler<WebSocketFrame> {

    protected String redisKey(String fix) {
        return getClass().getSimpleName() + ":" + fix;
    }

    //创建房间
    protected String redisKeyCreateRoom(String roomId) {
        return redisKey("create_room|" + roomId);
    }

    //房间剩余位置
    protected String redisKeyRoomLeft(String roomId) {
        return redisKey("room_left|" + roomId);
    }

    //超时会回调
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        LogMdc.setRequestId();
        log.debug("{}----userEventTriggered", ctx.channel().id().asShortText());
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (IdleState.READER_IDLE.equals(event.state())) { // 如果读通道处于空闲状态，说明没有接收到心跳命令
                log.error("{}----已等待30秒还没收到客户端发来的消息", ctx.channel().id().asShortText());
                NettyUtil.sendTextAndClose(ctx.channel(), EnumNettyErr.TIMEOUT.getR(""));
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        LogMdc.setRequestId();
        log.debug("{}----exceptionCaught:{}", ctx.channel().id().asShortText(), cause.getMessage());
        super.exceptionCaught(ctx, cause);

        Channel channel = ctx.channel();
        if (channel.isActive()) {
            channel.close();
        }
    }

    /**
     * 房间可加入人数最大上限
     */
    protected abstract Integer roomJoinMax();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame webSocketFrame) throws Exception {
        LogMdc.setRequestId();
        log.debug("id:{}", ctx.channel().id());
        if (!(webSocketFrame instanceof TextWebSocketFrame)) {
            // 不接受文本以外的数据帧类型
            log.error("内容非法：非文本，断开链接");
            ctx.channel().writeAndFlush(WebSocketCloseStatus.INVALID_MESSAGE_TYPE).addListener(ChannelFutureListener.CLOSE);
            return;
        }

        TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) webSocketFrame;
        String msg = textWebSocketFrame.text();
        if ("ping".equalsIgnoreCase(msg)) {
            log.debug("{}----ping", ctx.channel().id().asShortText());
            return;
        }

        // 业务层处理数据
        log.debug("msg:{}", msg);
        NettyMsg nettyMsg = NettyMsg.parse(msg);
        //if (XStr.isNullOrEmpty(nettyMsg.getId())) {
        //    sendText(ctx.channel(), NettyMsg.getR("", 20047, "id miss"));
        //    return;
        //}

        NettyMsg r;
        switch (nettyMsg.getAction()) {
            case 1: //发消息
                r = handlerRoomMsg(ctx.channel(), nettyMsg);
                break;
            case 10: //创建房间
                r = handlerCreateRoom(ctx.channel(), nettyMsg);
                break;
            case 11: //加入房间
                r = handlerJoinRoom(ctx.channel(), nettyMsg);
                break;
            default:
                NettyUtil.sendTextAndClose(ctx.channel(), NettyMsg.getR("", 30105, "命令异常"));
                return;
        }

        if (r.err()) {
            NettyUtil.sendText(ctx.channel(), r);
        }
    }

    //房间通信
    final protected NettyMsg handlerRoomMsg(Channel channel, NettyMsg msg) {
        log.debug("{}----handlerMsg BEGIN", channel.id().asShortText());

        if (XStr.isNullOrEmpty(msg.getRoomId())) {
            return EnumNettyErr.MISS_ROOM_ID.getR("");
        }

        NettyRoomUser roomUser = getRoomUser(channel);
        if (XStr.isNullOrEmpty(roomUser.getId())) {
            return EnumNettyErr.MISS_USER_ID.getR(msg.getRoomId());
        }

        String roomId = roomUser.getRoomId();
        if (XStr.isNullOrEmpty(roomId)) {
            return EnumNettyErr.NOT_JOIN_ROOM.getR(msg.getRoomId());
        }

        if (!roomId.equals(msg.getRoomId())) {
            NettyMsg r = EnumNettyErr.ROOM_ERR.getR(roomId, "房间异常，通信失败");
            r.setData("user", roomId);
            r.setData("msg", msg.getRoomId());
            return r;
        }

        if (!XRedis.getBean().exists(redisKeyRoomLeft(msg.getRoomId()))) {
            return EnumNettyErr.ROOM_ID_ERR.getR(msg.getRoomId());
        }

        return handlerRoomMsgAfter(channel, msg);
    }

    //创建房间
    final protected NettyMsg handlerCreateRoom(Channel channel, NettyMsg msg) {
        log.debug("{}----handlerCreateRoom BEGIN", channel.id().asShortText());
        String roomId = getRoomId(channel);
        if (XStr.hasAnyText(roomId)) {
            return EnumNettyErr.CREATE_ROOM.getR(roomId);
        }

        String openid = NettyUtil.getOpenid(channel);


        roomId = XApp.getStrID();

        NettyMsg r = NettyMsg.getR(roomId);
        r.setId(openid);
        r.setAction(msg.getAction());

        //r.setId(roomId);

        log.debug("创建房间redis，并房主{}加入房间", r.getId());
        RMap<String, String> list = XRedis.getBean().getList(redisKeyCreateRoom(roomId));
        list.put(r.getId(), channel.id().asShortText());

        log.debug("设置房间可加入人数：{}", roomJoinMax());
        XRedis.getBean().increment(redisKeyRoomLeft(roomId), roomJoinMax().longValue());

        setRoomUser(channel, r.getRoomId(), r.getId(), true);

        log.debug("{}----通知主人，房间号：{}", channel.id().asShortText(), r.getRoomId());
        handlerCreateRoomAfter(channel, r);
        return r;
    }

    //加入房间
    final protected NettyMsg handlerJoinRoom(Channel channel, NettyMsg msg) {
        log.debug("{}----handlerJoinRoom BEGIN", channel.id().asShortText());

        if (XStr.isNullOrEmpty(msg.getRoomId())) {
            return EnumNettyErr.MISS_ROOM_ID.getR("");
        }

        String roomId = getRoomId(channel);
        if (XStr.hasAnyText(roomId)) {
            NettyMsg r = EnumNettyErr.USRE_JOINED_ROOM.getR(roomId);
            if (!roomId.equals(msg.getRoomId())) {
                r.setErrMsg("用户已加入其他房间");
            }
            return r;
        }


        String openid = NettyUtil.getOpenid(channel);
        msg.setId(openid);

        log.debug("{}房间", msg.getRoomId());
        if (!XRedis.getBean().exists(redisKeyRoomLeft(msg.getRoomId()))) {
            return EnumNettyErr.ROOM_ID_ERR.getR(msg.getRoomId());
        }
        Boolean joinRoom = XRedis.getBean().decrement(redisKeyRoomLeft(msg.getRoomId()));
        if (!joinRoom) {
            return EnumNettyErr.CREATE_JOIN_MAX.getR(msg.getRoomId());
        }

        NettyMsg r = handlerJoinRoomBegin(channel, msg);
        if (r.err()) {
            return r;
        }

        log.debug("{}加入房间", openid);
        RMap<String, String> list = XRedis.getBean().getList(redisKeyCreateRoom(msg.getRoomId()));
        list.put(msg.getId(), channel.id().asShortText());

        setRoomUser(channel, msg.getRoomId(), openid, false);

        handlerJoinRoomAfter(channel, r);

        return r;
    }

    abstract protected NettyMsg handlerJoinRoomBegin(Channel channel, NettyMsg msg);

    abstract protected void handlerJoinRoomAfter(Channel channel, NettyMsg msg);

    abstract protected NettyMsg handlerRoomMsgAfter(Channel channel, NettyMsg msg);

    protected NettyMsg handlerCreateRoomAfter(Channel ctx, NettyMsg msg) {
        return NettyMsg.getR("", 0);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LogMdc.setRequestId();
        super.channelActive(ctx);
        log.debug("{}----链接创建：{}", ctx.channel().id(), ctx.channel().remoteAddress());

        NettyUtil.mapChannel.put(ctx.channel().id().asShortText(), ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        LogMdc.setRequestId();
        super.channelInactive(ctx);
        log.debug("{}----链接断开：{}", ctx.channel().id(), ctx.channel().remoteAddress());
        log.debug("{}----token：{}", ctx.channel().id(), NettyUtil.getOpenid(ctx.channel()));

        NettyUtil.mapChannel.remove(ctx.channel().id().asShortText());

        NettyRoomUser roomUser = getRoomUser(ctx.channel());
        if (XStr.hasAnyText(roomUser.getRoomId())) {
            log.debug("删除redis房间记录");
            delRedisRoomData(roomUser.getRoomId(), roomUser.getRoomMaster(), roomUser.getId());
        }
    }

    /**
     * 删除redis房间记录
     */
    protected void delRedisRoomData(String roomId, Boolean master, String userId) {
        log.debug("删除redis房间记录");
        if (master) {
            log.debug("房主，删除房间所有东西");
            XRedis.getBean().getList(redisKeyCreateRoom(roomId)).delete();
            XRedis.getBean().del(redisKeyRoomLeft(roomId));
        } else {
            log.debug("非房主，删除房间入房记录，并返余量");
            XRedis.getBean().getList(redisKeyCreateRoom(roomId)).remove(userId);
            if (XRedis.getBean().exists(redisKeyRoomLeft(roomId))) {
                XRedis.getBean().increment(redisKeyRoomLeft(roomId));
            }
        }
    }

    /**
     * 房号
     *
     * @param channel
     */
    public String getRoomId(Channel channel) {
        NettyRoomUser user = getRoomUser(channel);
        return user.getRoomId();
    }

    /**
     * 用户ID 过滤或者获取channel用到
     *
     * @param channel
     */
    public String getRoomUserId(Channel channel) {
        NettyRoomUser user = getRoomUser(channel);
        return user.getId();
    }

    /**
     * 设置房间用户信息
     *
     * @param channel
     * @param roomId
     * @param id
     * @param master
     */
    public void setRoomUser(Channel channel, String roomId, String id, Boolean master) {
        NettyRoomUser user = new NettyRoomUser();
        user.setX(NettyUtil.getAttr(channel, "x"));
        user.setRoomId(roomId);
        user.setId(id);
        user.setRoomMaster(master);

        setRoomUser(channel, user);
    }

    public void setRoomUser(Channel channel, NettyRoomUser user) {
        NettyUtil.setAttr(channel, "room_user", XStr.toJsonStr(user));
    }

    public NettyRoomUser getRoomUser(Channel channel) {
        AttributeKey<String> key = NettyUtil.getAttrKey("room_user");
        String s = channel.attr(key).get();
        if (XStr.isNullOrEmpty(s)) {
            return new NettyRoomUser();
        }
        return XJson.parseObject(s, NettyRoomUser.class);
    }

    /**
     * 获取房间所有用户
     *
     * @param roomId
     */
    public Map<String, Channel> getRoomIdAllUser(String roomId, List<String> ignoreIds) {
        if (ignoreIds == null) {
            ignoreIds = new ArrayList<>();
        }

        RMap<String, String> list = XRedis.getBean().getList(redisKeyCreateRoom(roomId));

        Map<String, Channel> mapUser = new HashMap<>();
        for (Map.Entry<String, String> entry : list.entrySet()) {
            String id = entry.getKey();
            if (ignoreIds.contains(id)) continue; //过滤指定ID

            Channel channel = NettyUtil.mapChannel.get(entry.getValue());
            if (channel == null) continue;

            mapUser.put(id, channel);
        }

        return mapUser;
    }
}
